<?php

namespace App\Support;

use App\Models\BaseModel;
use App\Models\OpenShop\InvestmentPromotion\OpenShopSignOrderLogModel;
use Closure;
use Illuminate\Support\Collection;

class ChangeSupport
{

    public array $except = [
        "updatedAt",
        "updatedByUid",
        "createdByUid",
        "createdAt",
        "provinceCode",
        "cityCode",
        "districtCode"
    ];

    public array $changes = [];

    const FILED_TYPE_STRING          = 1;//字符串
    const FILED_TYPE_PRIVATE_FILE    = 2;//私有文件
    const FILED_TYPE_PUBLIC_FILE     = 3;//公有文件
    const FILED_TYPE_DATE_YMD        = 4;//年月日
    const FILED_TYPE_DATE_YMD_HIS    = 5;//年月日 时分秒
    const FILED_TYPE_ENUM            = 6;//枚举
    const FILED_TYPE_PRICE           = 7;//金额
    const FILED_TYPE_PRIVATE_PICTURE = 8;//私有图片
    const FILED_TYPE_PUBLIC_PICTURE  = 9;//公有图片
    const FILED_TYPE_SELECT_ENUM     = 10;//多选枚举

    const CHANGE_TYPE_SINGLE_INSERT = 1;//新增
    const CHANGE_TYPE_SINGLE_UPDATE = 2;//编辑

    /**
     * @var ChangeSupport|null
     */
    private static ?ChangeSupport $instance = null;

    private function __construct()
    {

    }


    public static function getInstance(): ChangeSupport
    {
        if (!self::$instance instanceof self) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * 单模型下数据变更
     *
     * @param BaseModel $model 模型实例化对象
     * @param array|Closure $map 变更条件
     * @param array $update 更新数据源
     * @param string $changeTheme 变更主题
     * @param array $translate 转换存储日志映射
     */
    public function singleUpdate(
        BaseModel $model,
        $map,
        array $update,
        string $changeTheme,
        array $translate,
        array $logFields = []
    ) {
        $originalModel    = $model->newQuery()->where($map)->orderBy("id", "desc")->first();
        $originalData     = $originalModel->getRawOriginal();
        $afterChangeModel = $originalModel->setRawAttributes($update);
        $afterChangeModel->save();
        $afterChangeData = $afterChangeModel->getRawOriginal();

        $afterChangeData = $logFields ? collect($afterChangeData)->only($logFields)->toArray() : $afterChangeData;

        $this->translateChanges(self::CHANGE_TYPE_SINGLE_UPDATE, $originalData, $afterChangeData, $changeTheme,
            $translate);
    }

    /**
     * 单模型下数据变更
     *
     * @param BaseModel $model 模型实例化对象
     * @param array $data
     * @param string $changeTheme 变更主题
     * @param array $translate 转换存储日志映射
     * @param array $logColumn
     */
    public function singleInsert(
        BaseModel $model,
        array $data,
        string $changeTheme,
        array $translate,
        array $logColumn = []
    ) {
        $model->newQuery()->insert($data);
        $afterChangeData = $logColumn ? collect($data)->only($logColumn)->toArray() : $data;
        $this->translateChanges(self::CHANGE_TYPE_SINGLE_INSERT, [], $afterChangeData, $changeTheme, $translate);
    }

    /**
     * 单独构造自定义数据
     *
     * @param string $changeTheme
     * @param array|null $data
     * @param array|null $translate
     */
    public function buildInsertChanges(string $changeTheme, array $data = null, array $translate = null)
    {
        $this->translateChanges(self::CHANGE_TYPE_SINGLE_INSERT, [], $data ?: [], $changeTheme, $translate ?: []);
    }

    public function buildUpdateChanges(
        string $changeTheme,
        array $beforeData,
        array $afterData,
        array $translate = null,
        array $logFields = []
    ) {
        $afterData = $logFields ? collect($afterData)->only($logFields)->toArray() : $afterData;
        $this->translateChanges(self::CHANGE_TYPE_SINGLE_UPDATE, $beforeData, $afterData ?: [], $changeTheme,
            $translate ?: []);
    }

    /**
     * 特殊附件模型变更记录
     *
     * @param BaseModel $model
     * @param array|Closure $map
     * @param array $update
     * @param string $changeTheme
     * @param array $translate
     */
    public function attachBatchUpdate(
        BaseModel $model,
        $map,
        array $update,
        string $changeTheme,
        array $translate
    ) {
        $originalRes  = $model->newQuery()->where($map)->get();
        $originalData = $originalRes->groupBy("position")->map(function (Collection $v) {
            return $v->pluck("url")->toArray();
        })->toArray();
        $model->newQuery()->where($map)->update(["isDeleted" => 1]);
        if ($update) {
            $model->newQuery()->insert($update);
            $afterChangeData = collect($update)->groupBy("position")->map(function (Collection $v) {
                return $v->pluck("url")->toArray();
            })->toArray();
        } else {
            $afterChangeData = [];
        }

        $this->translateChanges(self::CHANGE_TYPE_SINGLE_UPDATE, $originalData, $afterChangeData, $changeTheme,
            $translate);
    }

    /**
     * @param int $changeType
     * @param array $beforeData
     * @param array $afterData
     * @param string $theme
     * @param array $translate
     */
    private function translateChanges(
        int $changeType,
        array $beforeData,
        array $afterData,
        string $theme,
        array $translate
    ) {
        array_push($this->changes, [
            "theme"      => $theme,
            "changeType" => $changeType,
            "data"       => collect($afterData)->except($this->except)->map(function ($v, $k) use (
                $beforeData,
                $translate
            ) {

                $beforeValue = $beforeData[$k] ?? "";
                $v           = ($v === null ? "" : $v);

                if (serialize($beforeValue) == serialize($v)) {
                    return [];
                }
                return [
                    "key"         => $k,
                    "beforeValue" => $beforeValue,
                    "afterValue"  => $v,
                    "beforeText"  => $beforeValue !== "" ? $this->getShowText($k, $beforeValue, $translate) : "",
                    "afterText"   => $this->getShowText($k, $v, $translate),
                    "type"        => $this->getType($k, $translate),
                    "description" => $this->getDescription($k, $translate),
                ];
            })->filter()->values()->toArray()
        ]);
    }

    /**
     * @param string $k
     * @param string|array $value
     * @param array $translate
     * @return mixed|string
     */
    private function getShowText(string $k, $value, array $translate)
    {
        $showText = $value;
        if (isset($translate[$k]) && isset($translate[$k]["type"])) {
            switch ($translate[$k]["type"]) {
                case self::FILED_TYPE_DATE_YMD:
                    $showText = formatDateYmd($value);
                    break;
                case self::FILED_TYPE_DATE_YMD_HIS:
                    $showText = formatDate($value);
                    break;
                case self::FILED_TYPE_ENUM:
                    if (isset($translate[$k]["enum"]) && isset($translate[$k]["enum"][$value])) {
                        $showText = $translate[$k]["enum"][$value];
                    }
                    break;
                case self::FILED_TYPE_PRICE:
                    $showText = is_int($value) ? bcdiv100($value) : "";
                    break;
                case self::FILED_TYPE_SELECT_ENUM:
                    if (isset($translate[$k]["enum"])) {
                        $showText = collect($translate[$k]["enum"])
                            ->only($valueArr = json_decode($value, true))
                            ->implode(",");
                    }
                    break;
            }
        }
        return $showText;
    }

    /**
     * @param string $k
     * @param array $translate
     * @return int|mixed
     */
    private function getType(string $k, array $translate)
    {
        if (isset($translate[$k]) && isset($translate[$k]["type"])) {
            return $translate[$k]["type"];
        }
        return self::FILED_TYPE_STRING;
    }

    /**
     * @param string $k
     * @param array $translate
     * @return mixed|string
     */
    private function getDescription(string $k, array $translate)
    {
        if (isset($translate[$k])) {
            if (is_string($translate[$k])) {
                return $translate[$k];
            }

            if (isset($translate[$k]["desc"])) {
                return $translate[$k]["desc"];
            }
        }
        return "-";
    }

    /**
     * 变更写入订单日志
     *
     * @param int $orderId
     * @param int $type
     * @param string $operatorName
     * @param int $uid
     */
    public function batchChangesWriteToOrderLog(int $orderId, int $type, string $operatorName = "系统", int $uid = 0)
    {
        $this->changes = collect($this->changes)->map(function ($v) {
            if (empty($v["data"])) {
                return [];
            }
            return $v;
        })->filter()->values()->toArray();

        if (!empty($this->changes)) {
            OpenShopSignOrderLogModel::query()->insert([
                "orderId"      => $orderId,
                "type"         => $type,
                "content"      => OpenShopSignOrderLogModel::TYPES[$type],
                "operatorName" => $operatorName,
                "createdAt"    => time(),
                "createdByUid" => $uid,
                "changes"      => json_encode($this->changes, JSON_UNESCAPED_UNICODE)
            ]);
        }
        $this->changes = [];
    }

    public function getChangeContent()
    {
        $changes       = $this->changes ? json_encode($this->changes, JSON_UNESCAPED_UNICODE) : "";
        $this->changes = [];
        return $changes;
    }

    public function pushChanges(array $changes)
    {
        array_push($this->changes, $changes);
    }
}
